﻿using System.Collections.Generic;
using System.Web;
using System.Web.Routing;
using EPiMVC.Routing;
using EPiMVC.UrlRewriting;
using EPiServer.Core;
using Moq;
using Xunit;

namespace EPiMVC.Tests.EPiServerRouteTests
{
    public class GetRouteDataCalledWith
    {
        EPiServerRoute route;
        HttpContextBase httpContext;
        RouteConfiguration routeConfiguration;
        PageReference pageReference = new PageReference();

        private void SetupRouteHandlerAndRequestContext(string requestedPath, string pagePath)
        {
            SetupRouteHandlerAndRequestContext(requestedPath, pagePath, new MvcPageData(pageReference));
        }

        private void SetupRouteHandlerAndRequestContext(string requestedPath, string pagePath, PageData pageToReturn)
        {
            Mock<IPageResolver> fakePageResolver = new Mock<IPageResolver>();
            fakePageResolver.Setup(resolver => resolver.GetPageByPath(pagePath)).Returns(pageToReturn);

            routeConfiguration = new RouteConfiguration();
            routeConfiguration.PageResolver = fakePageResolver.Object;

            route = new EPiServerRoute(routeConfiguration);

            Mock<HttpContextBase> fakeHttpContext = new Mock<HttpContextBase>();
            Mock<HttpRequestBase> fakeHttpRequest = new Mock<HttpRequestBase>();
            fakeHttpRequest.SetupGet(request => request.RawUrl).Returns(requestedPath);
            fakeHttpContext.SetupGet(context => context.Request).Returns(fakeHttpRequest.Object);

            httpContext = fakeHttpContext.Object;
        }

        [MvcPage]
        private class MvcPageData : PageData
        {
            public MvcPageData(PageReference pageLink) : base(pageLink) {}
        }

        [Fact]
        public void PathToPage_ReturnsRouteDataWithDefaultController()
        {
            string requestedPath = "/en/article/";
            PageData matchedPage = new MvcPageData(pageReference);
            SetupRouteHandlerAndRequestContext(requestedPath, requestedPath, matchedPage);

            RouteData routeData = route.GetRouteData(httpContext);

            Assert.NotNull(routeData.Values["controller"]);
            string expectedControllerName = GetDefaultControllerName(matchedPage);
            Assert.Equal<string>(expectedControllerName, routeData.Values["controller"].ToString());
        }

        private string GetDefaultControllerName(PageData pageData)
        {
            return routeConfiguration.ControllerNameConvention.GetDefaultControllerName(pageData.GetType());
        }

        [Fact]
        public void PathWithoutAction_ReturnsRouteDataWithDefaultActionFromConfiguration()
        {
            string requestedPath = "/en/article/";
            SetupRouteHandlerAndRequestContext(requestedPath, requestedPath);

            RouteData routeData = route.GetRouteData(httpContext);

            Assert.NotNull(routeData.Values["action"]);
            Assert.Equal<string>(routeConfiguration.DefaultActionName, routeData.Values["action"].ToString());
        }

        [Fact]
        public void PathToPage_ReturnsRouteDataWithCurrentPageReferenceSet()
        {
            string requestedPath = "/en/article/";
            SetupRouteHandlerAndRequestContext(requestedPath, requestedPath);

            RouteData routeData = route.GetRouteData(httpContext);

            Assert.NotNull(routeData.Values[EPiServerRoute.CurrentPageReferenceKey]);
            Assert.Equal<PageReference>(pageReference, (PageReference) routeData.Values[EPiServerRoute.CurrentPageReferenceKey]);
        }

        [Fact]
        public void PathWithAction_ReturnsRouteDataWithDefaultControllerAndSpecifiedAction()
        {
            string requestedPath = "/en/article/comment/";
            string pagePath = "/en/article/";
            PageData matchedPage = new MvcPageData(pageReference);
            SetupRouteHandlerAndRequestContext(requestedPath, pagePath, matchedPage);

            RouteData routeData = route.GetRouteData(httpContext);

            Assert.NotNull(routeData.Values["controller"]);
            Assert.NotNull(routeData.Values["action"]);
            Assert.Equal<string>(GetDefaultControllerName(matchedPage), routeData.Values["controller"].ToString());
            Assert.Equal<string>("comment", routeData.Values["action"].ToString());
        }

        [Fact]
        public void PathWithAction_ReturnsRouteDataWithCurrentPageReference()
        {
            string requestedPath = "/en/article/comment/";
            string pagePath = "/en/article/";
            SetupRouteHandlerAndRequestContext(requestedPath, pagePath);

            RouteData routeData = route.GetRouteData(httpContext);

            Assert.NotNull(routeData.Values[EPiServerRoute.CurrentPageReferenceKey]);
            Assert.Equal<PageReference>(pageReference, (PageReference)routeData.Values[EPiServerRoute.CurrentPageReferenceKey]);
        }

        [Fact]
        public void PathWithActionAndParameter_ReturnsRouteDataWithActionSetToLastSegmentBeforePagePath()
        {
            string requestedPath = "/en/article/deletecomment/123";
            string pagePath = "/en/article/";
            PageData matchedPage = new MvcPageData(pageReference);
            SetupRouteHandlerAndRequestContext(requestedPath, pagePath, matchedPage);

            RouteData routeData = route.GetRouteData(httpContext);
            
            Assert.NotNull(routeData.Values["controller"]);
            Assert.NotNull(routeData.Values["action"]);
            Assert.Equal<string>(GetDefaultControllerName(matchedPage), routeData.Values["controller"].ToString());
            Assert.Equal<string>("deletecomment", routeData.Values["action"].ToString());
        }

        [Fact]
        public void PathForPageWithRouteSpecificationContainingAction_ReturnsRouteDataWithAction()
        {
            string requestedPath = "/en/article/deletecomment";
            string pagePath = "/en/article/";
            SetupRouteHandlerAndRequestContext(requestedPath, pagePath, new RouteSpecifierPageData());

            RouteData routeData = route.GetRouteData(httpContext);

            Assert.NotNull(routeData.Values["action"]);
            Assert.Equal<string>("deletecomment", (routeData.Values["action"]).ToString());
        }

        [Fact]
        public void PathToPageWithRouteSpecificationWithTwoParameters_ReturnsRouteDataWithActionAndParameterSet()
        {
            string requestedPath = "/en/article/deletecomment/123";
            string pagePath = "/en/article/";
            SetupRouteHandlerAndRequestContext(requestedPath, pagePath, new RouteSpecifierPageData());

            RouteData routeData = route.GetRouteData(httpContext);

            Assert.NotNull(routeData.Values["action"]);
            Assert.NotNull(routeData.Values["id"]);
            Assert.Equal<string>("deletecomment", (routeData.Values["action"]).ToString());
            Assert.Equal<string>("123", (routeData.Values["id"]).ToString());
        }

        [Fact]
        public void PathToPageWithRouteSpecificationWithThreeParameters_ReturnsRouteDataWithActionAndParametersSet()
        {
            string requestedPath = "/en/article/deletecomment/123/optionalValue";
            string pagePath = "/en/article/";
            SetupRouteHandlerAndRequestContext(requestedPath, pagePath, new RouteSpecifierPageData());

            RouteData routeData = route.GetRouteData(httpContext);

            Assert.NotNull(routeData.Values["action"]);
            Assert.NotNull(routeData.Values["id"]);
            Assert.NotNull(routeData.Values["somethirdparam"]);
            Assert.Equal<string>("deletecomment", (routeData.Values["action"]).ToString());
            Assert.Equal<string>("123", (routeData.Values["id"]).ToString());
            Assert.Equal<string>("optionalValue", (routeData.Values["somethirdparam"]).ToString());
        }

        [MvcPage]
        [RouteEnding("action/id/somethirdparam")]
        private class RouteSpecifierPageData : PageData
        {
        }

        [Fact]
        public void PathWithParameterToNonRouteSpecifier_ReturnsRouteDataWithActionAndParameterList()
        {
            string requestedPath = "/en/article/deletecomment/123";
            string pagePath = "/en/article/";
            SetupRouteHandlerAndRequestContext(requestedPath, pagePath);

            RouteData routeData = route.GetRouteData(httpContext);

            Assert.NotNull(routeData.Values["action"]);
            Assert.NotNull(routeData.Values["parameters"]);
            Assert.Equal<string>("deletecomment", (routeData.Values["action"]).ToString());
            Assert.Equal<string>("123", ((List<string>)routeData.Values["parameters"])[0]);
        }

        [MvcPage]
        [DefaultController("ControllerName")]
        private class ControllerSpecifierPageData : PageData
        {
        }

        [Fact]
        public void PathToPageWithControllerAttribute_ReturnsRouteDataWithControllerFromAttribute()
        {
            string requestedPath = "/en/article/";
            SetupRouteHandlerAndRequestContext(requestedPath, requestedPath, new ControllerSpecifierPageData());

            RouteData routeData = route.GetRouteData(httpContext);

            Assert.NotNull(routeData.Values["controller"]);
            Assert.Equal<string>("ControllerName", (routeData.Values["controller"]).ToString());
        }

        [MvcPage]
        [DefaultAction("ActionName")]
        private class ActionSpecifierPageData : PageData
        {
        }

        [Fact]
        public void PathToPageWithActionAttribute_ReturnsRouteDataWithActionFromAttribute()
        {
            string requestedPath = "/en/article/";
            SetupRouteHandlerAndRequestContext(requestedPath, requestedPath, new ActionSpecifierPageData());

            RouteData routeData = route.GetRouteData(httpContext);

            Assert.NotNull(routeData.Values["action"]);
            Assert.Equal<string>("ActionName", (routeData.Values["action"]).ToString());
        }

        [Fact]
        public void InvalidPath_ReturnsNull()
        {
            string requestedPath = "/invalid/";
            string pagePath = "/en/article/";
            SetupRouteHandlerAndRequestContext(requestedPath, pagePath);

            RouteData routeData = route.GetRouteData(httpContext);

            Assert.Null(routeData);
        }

        [Fact]
        public void PathToPageWithoutMvcPageAttribute_ReturnsNull()
        {
            string requestedPath = "/nonmvcpage/";
            string pagePath = "/nonmvcpage/";
            SetupRouteHandlerAndRequestContext(requestedPath, pagePath, new PageData());

            RouteData routeData = route.GetRouteData(httpContext);

            Assert.Null(routeData);
        }

        [Fact]
        public void PathWithQueryStringParameter_ReturnsRouteDataWithQueryStringParameterAsRouteValue()
        {
            string parameterName = "param";
            string parameterValue = "123";
            string requestedPath = string.Format("/en/article/?{0}={1}", parameterName, parameterValue);
            string pagePath = "/en/article/";
            SetupRouteHandlerAndRequestContext(requestedPath, pagePath, new RouteSpecifierPageData());

            RouteData routeData = route.GetRouteData(httpContext);

            Assert.NotNull(routeData.Values[parameterName]);
            Assert.Equal<string>(parameterValue, (routeData.Values[parameterName]).ToString());
        }

        [Fact]
        public void PathWithTwoQueryStringParameters_ReturnsRouteDataWithQueryStringParametersAsRouteValue()
        {
            string parameter1Name = "param";
            string parameter1Value = "123";
            string parameter2Name = "secondParam";
            string parameter2Value = "456";
            string requestedPath = string.Format("/en/article/?{0}={1}&{2}={3}",
                parameter1Name, parameter1Value, parameter2Name, parameter2Value);
            string pagePath = "/en/article/";
            SetupRouteHandlerAndRequestContext(requestedPath, pagePath, new RouteSpecifierPageData());

            RouteData routeData = route.GetRouteData(httpContext);

            Assert.NotNull(routeData.Values[parameter1Name]);
            Assert.Equal<string>(parameter1Value, (routeData.Values[parameter1Name]).ToString());
            Assert.NotNull(routeData.Values[parameter2Name]);
            Assert.Equal<string>(parameter2Value, (routeData.Values[parameter2Name]).ToString());
        }
    }
}
